昨天大概說明了專案會切分哪些服務。今天開始想要逐步完成 Auth Service 中的功能,顧名思義,這項服務是為了驗證與授權的功能存在。
預計透過 Spring Security 與 JWT Token 來完成註冊、資料庫數據登入與第三方登入相關功能的實作,所以會先花一天的篇幅先介紹一下 Spring Security ~
為了更容易理解這個框架,接下來想以 社區保全系統 來意象化 Spring Security。在這個系統中,對於這個系統而言,開發者扮演的角色是 社區管委會 ,負責 制定 所有的安全規則。
這個系統中有兩個核心組件:
UserDetailsService
:像是門口的 保全人員 ,負責執行驗證。UserDetails
:就像發給社區住戶的 記名門禁卡 ,包含用戶資訊與權限。Spring Security 是 Spring Framework 中的其中一個模組,用來簡化處理應用程式的「安全性」。
就像一個完善的保全系統,這個系統主要負責規劃兩件事情:
首次接觸登入功能時,最容易搞混的就是這兩個名詞,用一個表格來比較,會更清楚:
比較項目 | Authentication (驗證) | Authorization (授權) |
---|---|---|
目的 | 確認使用者的身份是否合法 | 確認已登入的使用者有權限存取哪些資源 |
問的問題 | 「你是你所聲稱的那個人嗎?」 | 「你有權限執行這個操作嗎?」 |
對應產出 | 一個代表「已登入使用者」的 Authentication 物件 |
存取權限的決策 (允許 Allow / 拒絕 Deny) |
舉例 | 保全在門口要求你出示門禁卡,證明你是住戶 | 門禁卡中的權限資訊搭配相對應的控管機制,決定了電梯會讓你停在哪些樓層 |
除了驗證、授權的基本功能外,Spring Security 也提供了有效抵禦如跨站腳本攻擊(XSS)、跨站請求偽造(CSRF)、Session 劫持等常見威脅的解決方案。
保全系統主要透過兩個核心介面來完成驗證,UserDetailsService
和 UserDetails
。延續上面的比喻,它們的關係就好比「保全人員」與「門禁卡」。
比較項目 | UserDetails | UserDetailsService |
---|---|---|
比喻 | 門禁卡 | 保全人員 |
角色 | 資料容器 (Data Holder) | 資料載入器 (Data Loader) |
職責 | 「是什麼」- 描述使用者的資訊、狀態與權限 | 「怎麼做」- 描述載入使用者資訊的方法 |
核心 | 一組 get...() 方法,用來取得帳號、密碼、權限等資料 |
一個 loadUserByUsername() 方法,用來執行查詢 |
getAuthorities()
)等。之後建立的 UserEntity
就會實作這個介面,等於是為每個用戶發一張標準格式的「門禁卡」。loadUserByUsername
。實作這個介面,就等於是在教「保全人員」:當拿到使用者帳號時,要去哪裡找出對應的「門禁卡」資訊。UserDetailsService
) 拿著使用者輸入的帳號,去找出這個人對應的門禁卡資訊 (UserDetails
),以進行後續的密碼比對。為了安全地處理使用者在註冊與登入時的密碼,我們需要一套可靠的加密與比對機制。
Spring Security 提供了 PasswordEncoder
介面,該介面規定了兩種方法:
方法 | 說明 |
---|---|
encode |
用於加密,將傳入的原始密碼,透過加密演算法轉換成一長串無法被逆向解開的雜湊值 (Hash)。 |
matches |
用於比對,將使用者登入時輸入的密碼加密後,與資料庫中儲存的加密密碼進行比對,回傳 true 或 false 。 |
最常用的加密演算法是 BCrypt (BCryptPasswordEncoder
),它會在加密過程中隨機加鹽 (Salting),確保就算兩個使用者的原始密碼相同,存進資料庫的加密結果也絕對不同,大幅提升安全性。
通常我們會在 SecurityConfig
中定義 PasswordEncoder
:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
該物件實作了PasswordEncoder介面,並以BCrypt演算法實作加密與比對。
只需要在 pom.xml
中加入依賴即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>